/**
 * 
 */
package Verifier.FiatShamirProofsVerification;

import Verifier.ArithmeticSystem.ArithmeticConvertor;
import Verifier.ArithmeticSystem.FieldElement;
import Verifier.ArithmeticSystem.GroupElement;
import Verifier.ByteTree.ByteTree;
import Verifier.ByteTree.Leaf;
import Verifier.ByteTree.Node;

/**
 * The class executes Algorithm No.16 to verify the proof of Shuffle Of Commitments.
 * 
 * @author Moshe Baavur
 *
 */
public class ShuffleOfCommitmentsProofVerifier extends ProofVerifier {
	GroupElement[] elementB;
	GroupElement elementATag;
	GroupElement[] elementBTag;
	GroupElement elementCTag;
	GroupElement elementDTag;
	
	FieldElement elementK_A;
	FieldElement[] elementK_B;
	FieldElement elementK_C;
	FieldElement elementK_D;
	FieldElement[] elementK_E;
	
	GroupElement elementA;

	/**
	 * Initializes the proof with the correct information.
	 * 
	 * @param info the proof info which has all the needed arguments initialized using the first init method.
	 */
	public ShuffleOfCommitmentsProofVerifier(FiatShamirProofInfo info) {
		super(info);
		
		this.elementB = null;
		this.elementATag = null;
		this.elementBTag = null;
		this.elementCTag = null;
		this.elementDTag = null;
		
		this.elementK_A = null;
		this.elementK_B = null;
		this.elementK_C = null;
		this.elementK_D = null;
		this.elementK_E = null;
		
		this.elementA = null;
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#checkInputValidity()
	 */
	@Override
	protected boolean checkInputValidity() {
		// no checking is needed at the moment - the input parameters are controlled by us
		return (true);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#interpretCommitment()
	 */
	@Override
	protected boolean interpretCommitment() {
		// interpret the commitment
		ByteTree commitmentTree = new ByteTree(proofInfo.proofCommitment());

		if (commitmentTree.getFirstByteTreeElement() instanceof Leaf) {
			return (false);
		}

		Node commitmentRoot = (Node) commitmentTree.getFirstByteTreeElement();
		// the number of children should be 5 representing: B, A', B', C', D'
		if (5 != commitmentRoot.getDataLength()) {
			return (false);
		}
		
		this.elementB = null;
		this.elementATag = null;
		this.elementBTag = null;
		this.elementCTag = null;
		this.elementDTag = null;

		try {
			// the checks of them about being in the group is made in the
			// creation process
			elementB = ArithmeticConvertor.ToGroupElementArray(
					new ByteTree(commitmentRoot.getChild(0)),
					proofInfo.worldGroup());
			elementATag = ArithmeticConvertor.ToGroupElement(
					new ByteTree(commitmentRoot.getChild(1)),
					proofInfo.worldGroup());
			elementBTag = ArithmeticConvertor.ToGroupElementArray(
					new ByteTree(commitmentRoot.getChild(2)),
					proofInfo.worldGroup());
			elementCTag = ArithmeticConvertor.ToGroupElement(
					new ByteTree(commitmentRoot.getChild(3)),
					proofInfo.worldGroup());
			elementDTag = ArithmeticConvertor.ToGroupElement(
					new ByteTree(commitmentRoot.getChild(4)),
					proofInfo.worldGroup());
		} catch (Exception e) {
			return (false);
		}

		// now, check that the arrays are in the appropriate size
		if ((proofInfo.arraysSize() != elementB.length)
				|| (proofInfo.arraysSize() != elementBTag.length)) {
			return (false);
		}
		// all interpreted well
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#interpretReply()
	 */
	@Override
	protected boolean interpretReply() {
		// interpret the reply
		ByteTree replyTree = new ByteTree(proofInfo.proofReply());

		if (replyTree.getFirstByteTreeElement() instanceof Leaf) {
			return (false);
		}

		Node replyRoot = (Node) replyTree.getFirstByteTreeElement();
		// the number of children should be 5 representing: k_A, k_B, k_C, k_D,
		// k_E
		if (5 != replyRoot.getDataLength()) {
			return (false);
		}

		elementK_A = null;
		elementK_B = null;
		elementK_C = null;
		elementK_D = null;
		elementK_E = null;

		try {
			// the checks of them about being in the group is made in the
			// creation process
			elementK_A = ArithmeticConvertor.ToFieldElement(new ByteTree(
					replyRoot.getChild(0)), proofInfo.worldField());
			elementK_B = ArithmeticConvertor.ToFieldElementArray(new ByteTree(
					replyRoot.getChild(1)), proofInfo.worldField());
			elementK_C = ArithmeticConvertor.ToFieldElement(new ByteTree(
					replyRoot.getChild(2)), proofInfo.worldField());
			elementK_D = ArithmeticConvertor.ToFieldElement(new ByteTree(
					replyRoot.getChild(3)), proofInfo.worldField());
			elementK_E = ArithmeticConvertor.ToFieldElementArray(new ByteTree(
					replyRoot.getChild(4)), proofInfo.worldField());
		} catch (Exception e) {
			return (false);
		}

		// now, check that the arrays are in the appropriate size
		if ((proofInfo.arraysSize() != elementK_B.length)
				|| (proofInfo.arraysSize() != elementK_E.length)) {
			return (false);
		}

		// all interpreted well
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#computeSeed()
	 */
	@Override
	protected void computeSeed() throws Exception {
		// compute a seed s using the RandomOracle
		Node seedGroupNode = new Node(null);

		ByteTree creatorByteTree;
		try {
			creatorByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.worldGroup().getGroupCreatorAsGroupElement());
			ByteTree generatorsByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.independentGenerators());
			ByteTree pedersenByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.pedersenCommitments());
	
			seedGroupNode.addElement(creatorByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(generatorsByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(pedersenByteTree.getFirstByteTreeElement());
		} catch (Exception e) {
			return;
		}
		// create the seed from the byte tree
		createSeedFromByteTree(new ByteTree(seedGroupNode));
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#createRandomData()
	 */
	@Override
	protected void createRandomData() {
		this.elementA = (GroupElement)calculateRandomNumber(proofInfo.pedersenCommitments());
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#validateCalculations()
	 */
	@Override
	protected boolean validateCalculations() {
		// save the group's creator - remember that this is the creator of the group - G_q
		GroupElement groupCreator = proofInfo.worldGroup().getGroupCreatorAsGroupElement();
		
		// try validating B related calculations
		try {
			for (int i = 0 ; i < proofInfo.arraysSize(); i++) {
				GroupElement selB = (i > 0) ? elementB[i - 1] : proofInfo.independentGenerators()[0];
				
				GroupElement leftElement = (GroupElement) elementB[i].power(lastComputedChallenge).multiply(elementBTag[i]);
				GroupElement rightElement = (GroupElement) groupCreator.power(elementK_B[i].getElement()).multiply(selB.power(elementK_E[i].getElement()));
				if (!leftElement.equals(rightElement)) {
					return (false);
				}	
			}
		}catch (Exception e) {
			// couldn't validate B - something went wrong
			return (false);
		}
		
		// calculate c and d and validate them
		GroupElement elementC = null;
		
		// try validating C related calculations
		try {
			elementC = proofInfo.worldGroup().getGroupIdentityElement();

			for (int i = 0; i < proofInfo.arraysSize(); i++) {
				elementC = (GroupElement) elementC.multiply(proofInfo.pedersenCommitments()[i])
						.divide(proofInfo.independentGenerators()[i]);
			}
			
			GroupElement groupElementK_C = (GroupElement)groupCreator.power(elementK_C.getElement());
			if (!elementC.power(lastComputedChallenge).multiply(elementCTag).equals(groupElementK_C)) {
				return (false);
			}
		} catch (Exception e) {
			// couldn't create the element C or convert the calculation of g^k_C into an element in the world group
			return (false);
		}
		
		GroupElement elementD = null;
		// try validating D related calculations
		try {
			elementD = (GroupElement) elementB[proofInfo.arraysSize() - 1].divide(proofInfo.independentGenerators()[0].power(totalExponent));
			GroupElement groupElementK_D = (GroupElement)groupCreator.power(elementK_D.getElement());
			if (!elementD.power(lastComputedChallenge).multiply(elementDTag).equals(groupElementK_D)) {
				return (false);
			}
		} catch (Exception e) {
			// couldn't create the element D or the calculation of g^k_D into an element in the world group
			return (false);
		}
		
		// try validating A related calculations
		try {
			// calculate h^k_e multiplication
			GroupElement elementMultiH = (GroupElement) groupCreator.power(elementK_A.getElement());
			for (int i = 0; i < proofInfo.arraysSize(); i++) {
				elementMultiH = (GroupElement) elementMultiH.multiply(proofInfo.independentGenerators()[i].power(elementK_E[i].getElement()));
			}
			if (!elementA.power(lastComputedChallenge).multiply(elementATag).equals(elementMultiH)) {
				return (false);
			}
		}catch (Exception e) {
			// couldn't validate A - something went wrong
			return (false);
		}
		
		// the proof was validated successfully
		return (true);
	}
}
